﻿using Common;
using Linguist.Acoustic;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using Util;

namespace Linguist.LexTree
{
    /// <summary>
    /// Manages a single entry point.
    /// </summary>
    public class EntryPoint
    {
        /// <summary>
        /// the parent tree of this entry point
        /// </summary>
        HMMTree parent = null;
        IUnit baseUnit;
        /// <summary>
        /// second units and beyond start here
        /// </summary>
        Node baseNode;
        /// <summary>
        /// the base node for this entry point
        /// </summary>
        public Node BaseNode
        {
            get { return baseNode; }
            //set { baseNode = value; }
        }      

        Dictionary<IUnit, Node> unitToEntryPointMap;

        List<IPronunciation> singleUnitWords;

        int nodeCount;
        List<IUnit> rcSet;
        float totalProbability;
        /// <summary>
        /// the probability for all words reachable from this node
        /// Accumulates the probability for this entry point
        /// </summary>
        public float TotalProbability
        {
            get { return totalProbability; }
            
            set 
            {
                if (value > totalProbability)
                    totalProbability = value; 
            }
        }

        /// <summary>
        /// Creates an entry point for the given unit
        /// </summary>
        /// <param name="baseUnit">the EntryPoint is created for this unit</param>
        /// <param name="_parent"></param>
        public EntryPoint(IUnit baseUnit,HMMTree _parent) 
        {
            this.parent = _parent;
            this.baseUnit = baseUnit;
            this.baseNode = new Node(LogMath.LOG_ZERO);
            this.unitToEntryPointMap = new Dictionary<IUnit, Node>();
            this.singleUnitWords = new List<IPronunciation>();
            this.totalProbability = LogMath.LOG_ZERO;
        }

        /// <summary>
        /// Given a left context get a node that represents a single set of entry points into this unit
        /// </summary>
        /// <param name="leftContext">leftContext the left context of interest</param>
        /// <returns>the node representing the entry point</returns>
        public Node getEntryPointsFromLeftContext(IUnit leftContext) 
        {
            return unitToEntryPointMap[leftContext];
        }


        /// <summary>
        /// Once we have built the full entry point we can eliminate some fields 
        /// </summary>
        public void freeze() 
        {
            foreach (Node node in unitToEntryPointMap.Values) 
            {
                node.freeze();
            }
            singleUnitWords = null;
            rcSet = null;
        }

        /// <summary>
        /// Adds a one-unit word to this entry point. Such single unit words need to be dealt with specially.
        /// </summary>
        /// <param name="p">the pronunciation of the single unit word</param>
        public void addSingleUnitWord(IPronunciation p) 
        {
            singleUnitWords.Add(p);
        }

        /// <summary>
        /// Gets the set of possible right contexts that we can transition to from this entry point
        /// </summary>
        /// <returns>the set of possible transition points.</returns>
        private List<IUnit> getEntryPointRC() 
        {
            if (rcSet == null) 
            {
                rcSet = new List<IUnit>();
                foreach (Node node in baseNode.getSuccessorMap().Values) 
                {
                    rcSet.Add(((UnitNode)node).getBaseUnit());
                }
            }
            return rcSet;
        }
       
        /// <summary>
        /// A version of createEntryPointMap that compresses common hmms across all entry points.
        /// </summary>
        public void createEntryPointMap() 
        {
            Dictionary<IHMM, Node> map = new Dictionary<IHMM, Node>();
            Dictionary<IHMM, HMMNode> singleUnitMap = new Dictionary<IHMM, HMMNode>();

            foreach (IUnit lc in parent.exitPoints) 
            {
                Node epNode = new Node(LogMath.LOG_ZERO);
                foreach (IUnit rc in getEntryPointRC()) 
                {
                    IHMM hmm = parent.hmmPool.getHMM(baseUnit, lc, rc, HMMPosition.BEGIN);
                    if (hmm == null)
                        continue;
                    Node addedNode=null;
                    if(map.ContainsKey(hmm))
                        addedNode = map[hmm];
                    if (addedNode == null) 
                    {
                        addedNode = epNode.addSuccessor(hmm, totalProbability);
                        map.Add(hmm,addedNode);
                    } 
                    else 
                    {
                        epNode.putSuccessor(hmm, addedNode);
                    }

                    nodeCount++;
                    connectEntryPointNode(addedNode, rc);
                }
                connectSingleUnitWords(lc, epNode, singleUnitMap);
                if (!unitToEntryPointMap.ContainsKey(lc))
                    unitToEntryPointMap.Add(lc, epNode);
            }
        }

        /// <summary>
        /// Connects the single unit words associated with this entry point.   The singleUnitWords list contains all
        /// single unit pronunciations that have as their sole unit, the unit associated with this entry point. Entry
        /// points for these words are added to the epNode for all possible left (exit) and right (entry) contexts.
        /// </summary>
        /// <param name="lc">the left context</param>
        /// <param name="epNode">the entry point node</param>
        /// <param name="map"></param>
        private void connectSingleUnitWords(IUnit lc, Node epNode, Dictionary<IHMM, HMMNode> map) 
        {
            if (singleUnitWords.Count!=0) 
            {    
                foreach (IUnit rc in parent.entryPoints) 
                {
                    IHMM hmm = parent.hmmPool.getHMM(baseUnit, lc, rc, HMMPosition.SINGLE);
                    if (hmm == null)
                        continue;
                    HMMNode tailNode=null;
                    if(hmm!=null && map.ContainsKey(hmm))
                        tailNode = map[hmm];

                    if (tailNode == null) 
                    {
                        tailNode = (HMMNode) epNode.addSuccessor(hmm, totalProbability);
                        map.Add(hmm, tailNode);
                    } 
                    else 
                    {
                        epNode.putSuccessor(hmm, tailNode);
                    }
                    WordNode wordNode;
                    tailNode.addRC(rc);
                    nodeCount++;

                    foreach (IPronunciation p in singleUnitWords) 
                    {
                        if (p.getWord() == parent.dictionary.getSentenceStartWord()) 
                        {
                            parent.initialNode = new InitialWordNode(p, tailNode);
                        } 
                        else 
                        {
                            float prob = parent.getWordUnigramProbability(p.getWord());
                            wordNode = tailNode.addSuccessor(p, prob);
                            if (p.getWord() == parent.dictionary.getSentenceEndWord()) 
                            {
                                parent.sentenceEndWordNode = wordNode;
                            }
                        }
                        nodeCount++;
                    }
                }
            }
        }


        /**
        /// Connect the entry points that match the given rc to the given epNode
         *
        /// @param epNode add matching successors here
        /// @param rc     the next unit
         */
        private void connectEntryPointNode(Node epNode, IUnit rc) 
        {
            foreach (Node node in BaseNode.getSuccessors()) 
            {
                UnitNode successor = (UnitNode) node;
                if (successor.getBaseUnit() == rc) 
                {
                    epNode.addSuccessor(successor);
                }
            }
        }


        /** Dumps the entry point */
        public void dump() 
        {
            Trace.WriteLine("EntryPoint " + baseUnit + " RC Followers: "
                    + getEntryPointRC().Count);
            int count = 0;
            List<IUnit> rcs = getEntryPointRC();
            Trace.WriteLine("    ");
            foreach (IUnit rc in rcs) 
            {
                Trace.WriteLine(Utilities.pad(rc.getName(), 4));
                if (count++ >= 12) {
                    count = 0;
                    Trace.WriteLine("    ");
                }
            }
            Trace.WriteLine("");
        }

    }
}
